home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / Apps / DevTools / eText5 / Source / eTImage.subproj / eTImage.m < prev    next >
Encoding:
Text File  |  1995-02-14  |  16.6 KB  |  592 lines

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //    FILENAME:    eTImage.m 
  3. //    SUMMARY:    Implementation of Image annotations with Action support
  4. //    SUPERCLASS:    Object
  5. //    INTERFACE:    None
  6. //    PROTOCOLS:    <Annotation,HTMDSupport,ASCIISupport,LaTeXSupport,Tool,
  7. //                InspectableTarget,DocNotification>
  8. //    AUTHOR:        Rohit Khare
  9. //    COPYRIGHT:    (c) 1994 California Institure of Technology, eText Project
  10. ///////////////////////////////////////////////////////////////////////////////
  11. //    IMPLEMENTATION COMMENTS
  12. //        A whole lot of interesting delegation has been introduced between
  13. //    eTImage and eTImageComponent.
  14. //        Tips for subclass implementors:
  15. //    * Feel free to access the ivars directly and avoid the extra messages...
  16. //    * ... but make sure to call updateGraphics to commit your changes.
  17. //    * YOU have to make sure the images are deallocated. Leave all of the other
  18. //        ivars alone in this class, but if you want to free these images, YOU
  19. //        have to free them in your subclass's -free.
  20. ///////////////////////////////////////////////////////////////////////////////
  21. //    HISTORY
  22. //    02/14/95:    In TestTwentyFourBitRGB on mono, it kept crashing on -backColor
  23. //    02/11/95:    Attempted to modify captioning process to avoid redraw.
  24. //    10/30/94:    Modified to support <InspectableTarget>
  25. //    07/16/94:    Rewritten from scratch, integrating ImageAnnotation and Image
  26. ///////////////////////////////////////////////////////////////////////////////
  27.  
  28. #import "eTImage.h"
  29. #import "eTImageUI.h"
  30. #define _eTImageVERSION    10
  31. #define _eTImageVERSION_captions    22
  32.  
  33. @implementation eTImage
  34. //    id        theText
  35. //    id        etDoc
  36. //    
  37. //    id        imageComponent
  38. //    id        image
  39. //    id        altImageComponent
  40. //    id        altImage
  41. //    char     *description
  42. //    NXSize    size
  43. //    BOOL    usesButtonStyle
  44. //    BOOL    isDraggable
  45. //    BOOL    highlighted
  46.  
  47. // "Delegate" methods 
  48.     // User Interaction
  49.  
  50. - click:(NXEvent*)e
  51.     { return self; }
  52. - doubleClick:(NXEvent*)e
  53. {    
  54.     NXAtom fname = [imageComponent currentPath];
  55.  
  56.     if (*fname && !access(fname, F_OK|R_OK)) {
  57.         // we need to flip the location y coord
  58.         NXPoint pt;
  59.         NXSize    sz;
  60.         id         img;
  61.         
  62.         pt = e->location;
  63.         img = [imageComponent icon];
  64.         [theText convertPoint:&pt fromView:nil];
  65.         [img getSize:&sz];
  66.         pt.x -= sz.width/2;
  67.         pt.y += sz.height/2;
  68.         [[Application workspace]
  69.             openFile:fname fromImage: img
  70.             at: &pt inView:theText];
  71.     }
  72.     return self; 
  73. }
  74.  
  75. - inspect:(NXEvent*)e
  76. {
  77.     [[NXApp inspector] inspect:self];
  78.     return self; 
  79. }
  80. - (id <Inspectable>) inspectableDelegate {
  81.     return [[eTImageUI new] setAnnotation:self]; }
  82.  
  83. - drag: (Pasteboard *)draggingPboard image:(NXImage **)proxyImage
  84. {
  85.     NXAtom fname = [imageComponent currentPath];
  86.  
  87.     if (*fname && !access(fname, F_OK|R_OK)) {
  88.         [imageComponent writeComponentToPboard:draggingPboard];
  89.         *proxyImage = [imageComponent icon];
  90.     } else {
  91.         NXRunAlertPanel("eTImage","Cannot access %s. Try saving the document.","OK",NULL,NULL,fname?fname:"the image");
  92.         draggingPboard=nil;
  93.         *proxyImage=nil;
  94.     }
  95.     return self;
  96. }
  97.  
  98. // "Client" methods -- feel free to access ivars directly.
  99. // Getter/Setter methods
  100.  
  101. - setImageComponent: newImageComponent
  102. {
  103.     if (![newImageComponent isKindOf:[eTImageComponent class]])
  104.         return nil;
  105.     if (imageComponent != newImageComponent) {
  106.         [imageComponent free];
  107.         imageComponent = newImageComponent;
  108.     }
  109.     image = [imageComponent theImage];
  110.     if (image) [image getSize:&size];
  111.     else size.width = size.height = 0.0;
  112.     captionMode = NO;
  113.     // don't drag out shared images. Conflict with subclasses that drag out other data.
  114.     // Reveals design flaw.
  115.     if ([imageComponent isShared] && ([self class] == [eTImage class])) isDraggable = NO;
  116.     return self;
  117. }
  118. - setAltImageComponent: newAltImageComponent
  119. {
  120.     if (![newAltImageComponent isKindOf:[eTImageComponent class]])
  121.         return nil;
  122.     altImageComponent = newAltImageComponent;
  123.     altImage = [altImageComponent theImage];
  124.     return self;
  125. }
  126. - setDescription: (const char *) newDescription
  127. {    
  128.     description=realloc(description,(strlen(newDescription)+1)*sizeof(char));
  129.     strcpy(description,newDescription);
  130.     if (captionMode) [self setCaptionMode];
  131.     [etDoc touch];
  132.     return self;
  133. }
  134. - setUsesButtonStyle:(BOOL)newState
  135. {
  136.     usesButtonStyle = newState; 
  137.     return self;
  138. }
  139. - setDraggable:(BOOL)newState
  140. {
  141.     isDraggable=newState;
  142.     return self;
  143. }
  144. - setSize:(const NXSize *)newSize
  145. {
  146.     // I don't quite know what to do with this.
  147.     // How do we factor in the Component's isMutable semantics?
  148.     // should this do a touch?
  149.     if ([imageComponent isMutable] && !captionMode) {
  150.         size = *newSize;
  151.         [image setSize:&size];
  152.         //[imageComponent touch];
  153.         if ([altImageComponent isMutable]) {
  154.             [altImage setSize:&size];
  155.             //[altImageComponent touch];
  156.         }
  157.         [etDoc touch];
  158.     }
  159.     return self;
  160. }
  161.  
  162. - setCaptionMode {
  163.     if (description && theText) {
  164.         NXRun *theRun = [theText runForAnnotation:self];
  165.  
  166.         if (theRun && description) {
  167.             size.width = [theRun->font getWidthOf:description];
  168.             size.height = [theRun->font pointSize];
  169.             captionMode = YES;
  170.             [self setUsesButtonStyle: YES];
  171.         } else if (description) {
  172.             size.width = [[theText font] getWidthOf:description];
  173.             size.height = [[theText font] pointSize];
  174.             captionMode = YES;
  175.             [self setUsesButtonStyle: YES];
  176.         }
  177.     }
  178.     return self;
  179. }
  180. - setState:(BOOL) newState {state = newState; return [self updateGraphics];}
  181. - eTextObj 
  182.     {return theText;}
  183. - etDoc 
  184.     {return etDoc;}
  185. - imageComponent
  186.     {return imageComponent;}
  187. - altImageComponent 
  188.     {return altImageComponent;}
  189. - (char *) description 
  190.     {return description;}
  191. - (char **) getDescription                 // be careful of reallocing this
  192.     {return &description;}
  193. - (BOOL) usesButtonStyle 
  194.     {return usesButtonStyle;}
  195. - (BOOL) isDraggable 
  196.     {return isDraggable;}
  197. - (NXSize *) size
  198.     {return &size;}
  199. - (BOOL) state {return state;}
  200. - (BOOL) captionMode {return captionMode;}
  201. // State Flushing
  202. - updateGraphics
  203. {
  204.     [theText perform:@selector(calcLine) with:nil afterDelay:0 cancelPrevious:YES];
  205.     [[theText superview] perform:@selector(display) with:nil afterDelay:0 cancelPrevious:YES];
  206.     return self;
  207. }
  208.  
  209. // "Private" Methods; really, default implementations
  210.     // Lifecycle
  211. + toolAwake:theApp
  212. {
  213.     const char *const * types;    
  214.     int            i;
  215.  
  216.     [theApp   registerAnnotation: [eTImage class] 
  217.                             name: "eTImage"
  218.                     RTFDirective: "eTImage"
  219.                        menuLabel: "Insert Image..."
  220.                          menuKey: 'I'
  221.                         menuIcon: (NXImage *) nil];
  222.     types = [NXImage imagePasteboardTypes];
  223.     for(i=0; types[i]; i++)
  224.         [theApp registerType:types[i] for:[eTImage class]];
  225.     types = [NXImage imageFileTypes];
  226.     for(i=0; types[i]; i++)
  227.         [theApp registerType:NXCreateFileContentsPboardType(types[i])
  228.                 for:[eTImage class]];
  229.     return self;
  230. }
  231. - init
  232. {    
  233.     [super init];
  234.     if (!description){
  235.         description = (char *)malloc(2*sizeof(char));
  236.         strcpy(description, "");
  237.     }
  238.     isDraggable = YES;
  239.     // everything else is automatically nil or NO
  240.     imageComponent = altImageComponent = nil;
  241.     captionMode = NO;
  242.     return self;
  243. }
  244. - free
  245. {    
  246.     [[NXApp inspector] inspect:nil]; 
  247.     [etDoc unregisterNotification:self];
  248.     etDoc=nil;
  249.     [imageComponent free];
  250.     [altImageComponent free];
  251.     return self = [super free];
  252. }
  253.  
  254. - initFromPboard:thePB inDoc:theDoc linked:(BOOL) linked
  255. {
  256.     [self init];                        // this is a wierd call-chain. check?
  257.     etDoc = theDoc;
  258.     theText = [[etDoc docUI] eTextObj]; // consistency checking
  259.     [etDoc registerNotification:self];
  260.     
  261.     if ((!thePB) && !imageComponent) {
  262.         // initialize as a null image as if from a menu selection
  263.         imageComponent = [eTImageComponent newImageNamed:"eTImageComponentIcon"];
  264.         [self setImageComponent:imageComponent];
  265.     } else if (thePB) {
  266.         imageComponent = [[eTImageComponent alloc] 
  267.                             initInDoc:theDoc linked:linked];
  268.         [self setImageComponent:
  269.             [imageComponent readComponentFromPboard:thePB]];
  270.     }
  271.     return self;
  272. }
  273.  
  274.     // Drawing
  275. - calcCellSize:(NXSize *)theSize
  276. {
  277.     if (captionMode) [self setCaptionMode]; //Recalcs font-sizes
  278.     *theSize = size;
  279.     if (usesButtonStyle) {
  280.         theSize->width += 8; 
  281.         theSize->height += 8;
  282.     }
  283.     return self;
  284. }
  285.  
  286. - drawSelf:(const NXRect *)cellFrame inView:view
  287. {
  288.     NXPoint    point;
  289.     NXRect  bounds;
  290.     NXRun     *theRun;
  291.     
  292.     if (!etDoc || !theText) {
  293.         theText = view;
  294.         etDoc = [theText etDoc];
  295.         [etDoc registerNotification:self];
  296.     }
  297.     
  298.     [view getBounds:&bounds];
  299.     PSgsave();
  300.     point = cellFrame->origin;
  301.     point.y += cellFrame->size.height;
  302.     if (usesButtonStyle) {
  303.         NXDrawButton(cellFrame, &bounds);
  304.         point.x += 4; point.y -= 4;
  305.     } else {
  306.         //if ([theText shouldDrawColor])
  307.         //    NXSetColor([theText backgroundColor]);
  308.         //else
  309.             PSsetgray([theText backgroundGray]);
  310.         NXRectFill(cellFrame);
  311.     }
  312.     if (captionMode)
  313.         theRun = [theText runForAnnotation:self];
  314.     if (captionMode && theRun) {
  315.         if ([theText shouldDrawColor])
  316.             NXSetColor([theText runColor:theRun]);
  317.         else
  318.             PSsetgray([theText runGray:theRun]);
  319.         PSmoveto(point.x, point.y + (([theRun->font metrics])->descender * [theRun->font pointSize]));
  320.         [theRun->font set];
  321.         PSshow(description);
  322.     } else {
  323.         [((state && !usesButtonStyle) ? altImage : image) 
  324.             composite:NX_SOVER toPoint:&point];
  325.     }
  326.     PSgrestore();
  327.     return self;
  328. }
  329.  
  330. - highlight:(const NXRect *)rect inView:view lit:(BOOL)flag
  331. {
  332.     if (highlighted != flag) {
  333.         highlighted = flag;
  334.         //NXHighlightRect(cellFrame);
  335.     }
  336.     return self;
  337. }
  338.  
  339. - (BOOL)    trackMouse:(NXEvent *)theEvent
  340.             inRect:(const NXRect *)cellFrame
  341.             ofView:view
  342. {    // See chapter 7, Modal Event Loops in Prog Dynamics
  343.     NXPoint            point,offset,mLoc;
  344.     NXRect            tracker, bounds;
  345.     register int    inside;
  346.     int                shouldLoop = YES;
  347.     int                oldMask;
  348.     NXEvent            *nextEvent,saveEvent;
  349.     Pasteboard        *dragPasteboard;
  350.     NXImage            *proxyImage;
  351.     BOOL            inTextSel;    // you can't drag out of selected text.
  352.     NXSelPt            begin, end;
  353.     int                posn;
  354.     NXRun            *theRun;
  355.     
  356.     [view getBounds:&bounds];
  357.     tracker = *cellFrame;
  358.     NXIntersectionRect(&bounds, &tracker);
  359.     tracker.size.width += 16; tracker.size.height += 16; tracker.origin.x -=8; tracker.origin.y -= 8;
  360.     point = cellFrame->origin;
  361.     point.y += cellFrame->size.height;
  362.     saveEvent = *theEvent;
  363.  
  364.     PSgsave();
  365.     if (usesButtonStyle) {
  366.         NXDrawGrayBezel(cellFrame, &bounds);
  367.         point.x += 4; point.y -= 4;
  368.     }
  369.  
  370.     if (captionMode)
  371.         theRun = [theText runForAnnotation:self];
  372.     if (captionMode && theRun) {
  373.         if ([theText shouldDrawColor])
  374.             NXSetColor([theText runColor:theRun]);
  375.         else
  376.             PSsetgray([theText runGray:theRun]);
  377.         PSmoveto(point.x, point.y + (([theRun->font metrics])->descender * [theRun->font pointSize]));
  378.         [theRun->font set];
  379.         PSshow(description);
  380.         NXHighlightRect(cellFrame);
  381.     } else {
  382.         if (usesButtonStyle)
  383.             NXHighlightRect(cellFrame);
  384.         else 
  385.             [(state ? image : altImage) composite:NX_SOVER toPoint:&point];
  386.     }
  387.  
  388.     PSgrestore();
  389.     NXPing(); [[view window] flushWindow];
  390.  
  391.     [view getSel:&begin :&end];
  392.     posn = [view positionForAnnotation:self];
  393.     if ((posn >= begin.cp) && (posn <= end.cp)) inTextSel = YES;
  394.     else inTextSel = NO;
  395.     oldMask = [[view window] addToEventMask:NX_LMOUSEDRAGGEDMASK];
  396.     while (shouldLoop) {
  397.         nextEvent = 
  398.             [NXApp getNextEvent:(NX_LMOUSEUPMASK|NX_LMOUSEDRAGGEDMASK)];
  399.         mLoc = nextEvent->location;
  400.         [view convertPoint:&mLoc fromView:nil];
  401.         inside = [view mouse:&mLoc inRect:&tracker];
  402.         if ((!inside) && isDraggable && !inTextSel) {    
  403.             NXPoint imgLoc;
  404.             NXSize    imgSize;
  405.                     
  406.             dragPasteboard = [Pasteboard newName: NXDragPboard];
  407.             [self drag:dragPasteboard image: &proxyImage];
  408.             [proxyImage getSize:&imgSize];
  409.             // proxyImage's (fictional) origin in Text coords
  410.             imgLoc = saveEvent.location; 
  411.             [view convertPoint:&imgLoc fromView:nil];
  412.             imgLoc.x -= imgSize.width/2;
  413.             imgLoc.y += imgSize.height/2;
  414.  
  415.             offset.x = nextEvent->location.x - saveEvent.location.x;
  416.             offset.y = nextEvent->location.y - saveEvent.location.y;
  417.  
  418.             if (dragPasteboard && proxyImage) {
  419.                 [view     dragImage:proxyImage
  420.                         at:&imgLoc offset:&offset
  421.                         event:&saveEvent pasteboard:dragPasteboard
  422.                         source:self slideBack: YES];
  423.                 shouldLoop = NO;
  424.             }
  425.             
  426.             [self inspect:nextEvent];        
  427.         }
  428.         // ok, now is it inside the _actual_ bounds?
  429.         inside = [view mouse:&mLoc inRect:cellFrame];
  430.         if ((nextEvent->type == NX_LMOUSEUP) && shouldLoop && inside) {
  431.             // command-click  only inspects
  432.             // single-click inspects AND click:s
  433.             // double click inspects AND click:s AND doubleClick:s
  434.             if (nextEvent->data.mouse.click == 2)
  435.                 [self doubleClick:nextEvent];
  436.             else if (nextEvent->data.mouse.click == 1) {
  437.                 [self inspect:nextEvent];
  438.                 if (!((nextEvent->flags) & NX_COMMANDMASK)) 
  439.                     [self click:nextEvent];
  440.             }
  441.             shouldLoop = NO;
  442.         }
  443.         if (nextEvent->type == NX_LMOUSEUP)
  444.             shouldLoop = NO;    // mouse up outside rects.
  445.     }
  446.     [[view window] setEventMask:oldMask];
  447.  
  448.     PSgsave();
  449.     if (usesButtonStyle)
  450.         NXDrawButton(cellFrame,&bounds);
  451.     else 
  452.         NXEraseRect(cellFrame);
  453.  
  454.     if (captionMode)
  455.         theRun = [theText runForAnnotation:self];
  456.     if (captionMode && theRun) {
  457.         if ([theText shouldDrawColor])
  458.             NXSetColor([theText runColor:theRun]);
  459.         else
  460.             PSsetgray([theText runGray:theRun]);
  461.         PSmoveto(point.x, point.y + (([theRun->font metrics])->descender * [theRun->font pointSize]));
  462.         [theRun->font set];
  463.         PSshow(description);
  464.     } else {
  465.         [((state || usesButtonStyle) ? altImage : image) 
  466.             composite:NX_SOVER toPoint:&point];
  467.     }
  468.  
  469.     PSgrestore();
  470.     NXPing(); 
  471.     [[view window] flushWindow];
  472.  
  473.     return YES;
  474. }
  475.                 
  476.     // File I/O
  477.     // Here is a curious and inexplicable design decision:
  478.     // altImageComponents are never written.
  479.     // Why? I don't know. The implication is that the altImage is never the
  480.     // explicitly set (or desired) by the user.
  481. - readRichText:(NXStream *)stream forView:view
  482. {
  483.     int i,ver;
  484.     char shouldCap = 0;
  485.     NXSize temp;
  486.     
  487.     if (!etDoc || !theText) {
  488.         theText = view;
  489.         etDoc = [theText etDoc];
  490.         [etDoc registerNotification:self];
  491.     }
  492.     NXScanf(stream, "%d ", &ver);
  493.     if ((ver != _eTImageVERSION) && (ver != _eTImageVERSION_captions)) {
  494.         // bad version block.
  495.         NXLogError("eTImage found unparseable version %d at position %d",
  496.                     ver, NXTell(stream));
  497.         return nil;
  498.     }
  499.     NXScanf(stream, "%f %f %d", &(temp.height), &(temp.width), &i);
  500.     NXGetc(stream); // space-eater
  501.     if (i) {
  502.         if (description) free(description);
  503.         description = malloc(sizeof(char)*(i+1));
  504.         NXRead(stream, description, i);
  505.         description[i] = '\0';
  506.     }
  507.     NXGetc(stream);        // balances out the space at the end of %s
  508.     if (ver >= _eTImageVERSION_captions) {
  509.         NXScanf(stream, "%c ", &shouldCap);
  510.         shouldCap -= 'A';
  511.     }
  512.     [self setImageComponent:[[[eTImageComponent alloc] init] 
  513.                         readRichText:stream forView:view]];
  514.     if (shouldCap) {
  515.         //[self perform:@selector(setCaptionMode) with:nil afterDelay:0 cancelPrevious:NO];
  516.         [self setCaptionMode];
  517.         captionMode = YES;
  518.     }
  519.     return self;
  520. }
  521. - writeRichText:(NXStream *)stream forView:view
  522. {
  523.     if (!(size.height && size.width)) {
  524.         //sanity check failed. try reaquiring size from the image
  525.         image = [imageComponent theImage];
  526.         [image getSize:&size];
  527.         if (!(size.height && size.width)) {
  528.             // now we have real problems. go undercover here:
  529.             [imageComponent registerError];
  530.             image = [imageComponent theImage];
  531.             [image getSize:&size];
  532.         }
  533.     }
  534.     NXPrintf(stream, "%d %f %f %d %s %c ", _eTImageVERSION_captions,
  535.             size.height, size.width, strlen(description), description, captionMode + 'A');
  536.     [imageComponent writeRichText:stream forView:view];
  537.     return self;
  538. }
  539.  
  540. - writeHTML:(NXStream *)stream forView:view {
  541.     if (captionMode) {
  542.         NXPrintf(stream, "%v", description);
  543.     } else if (([self class] == [eTImage class]) && ![imageComponent isShared]) {
  544.         // Make the .gif point to the original file
  545.         NXPrintf(stream, "<A HREF=\"%V\">", [imageComponent componentName]);
  546.         [imageComponent writeHTML:stream forView:view withCaption:description];
  547.         NXPrintf(stream,"</A>");
  548.     } else {
  549.         [imageComponent writeHTML:stream forView:view withCaption:description];
  550.     }
  551.     return self;
  552. }
  553. - writeLaTeX:(NXStream *)stream forView:view
  554. {
  555.     if (captionMode) {
  556.         NXPrintf(stream, "%W", description);
  557.     } else {
  558.         NXPrintf(stream,"\\begin{figure}\n\\hrule\n\\vspace{12 pt}\n");
  559.         [imageComponent writeLaTeX:stream forView:view];
  560.         NXPrintf(stream,"\n\\vspace{12 pt}\n\\hrule\n\\caption{\\em %w.}\n\\label{%w}\n\\end{figure}\n[Figure \\ref{%w}]", description, [imageComponent componentName],[imageComponent componentName]);
  561.     }
  562.     return self;
  563. }
  564. - writeASCIIRef:(NXStream *)stream forView:view
  565.     {return [imageComponent writeASCIIRef:stream forView:view];}
  566.  
  567.     // Format encoding and support
  568. - writeComponentToPath:(NXAtom)path inFormat:(int) theFormat
  569. {
  570.     if(!etDoc)    NXLogError("etDoc is nil at %s %u",__FILE__,__LINE__);
  571.     if ((theFormat == ETFD_FMT) || !captionMode)
  572.         [imageComponent writeComponentToPath:path inFormat:theFormat];
  573.     if (([self class] == [eTImage class]) && (theFormat == HTMD_FMT) && !captionMode)
  574.         [imageComponent writeComponentToPath:path inFormat:ETFD_FMT];
  575.     return self;
  576. }
  577.     // Drag-and-Drop
  578. - addToPboard:pboard
  579. {
  580.     [imageComponent addToPboard:pboard];
  581.     return self;
  582. }
  583. - (NXDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag
  584. {
  585.     return (flag ? NX_DragOperationLink : NX_DragOperationAll);
  586. }
  587. - draggedImage:(NXImage *)image endedAt:(NXPoint *)screenPoint
  588.     deposited:(BOOL)flag
  589. {
  590.     return self;
  591. }
  592. @end